package com.one.blocks.rbac.controller;

import com.one.blocks.common.request.PageRequest;
import com.one.blocks.common.response.ResponseHelper;
import com.one.blocks.common.response.WebPageResponse;
import com.one.blocks.common.response.WebResponse;
import com.one.blocks.common.util.TreeUtil;
import com.one.blocks.mvc.controller.BaseController;
import com.one.blocks.mvc.enums.MvcResponseEnum;
import com.one.blocks.mvc.util.ScopeWebRequestHelper;
import com.one.blocks.mvc.valid.Custom;
import com.one.blocks.mvc.valid.Update;
import com.one.blocks.mvc.valid.UpdateSelective;
import com.one.blocks.mvc.valid.constraints.NoAllocatedData;
import com.one.blocks.rbac.controller.query.SysMenuQuery;
import com.one.blocks.rbac.controller.vo.SysMenuGrantVo;
import com.one.blocks.rbac.controller.vo.SysMenuTreeVo;
import com.one.blocks.rbac.controller.vo.SysMenuVo;
import com.one.blocks.rbac.convert.SysMenuConvert;
import com.one.blocks.rbac.domain.SysMenu;
import com.one.blocks.rbac.log.Log;
import com.one.blocks.rbac.manager.SysMenuManager;
import com.one.blocks.rbac.util.SubjectHelper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.one.blocks.rbac.constant.RbacConstant.ScopeRequest.VALID_MENU_ID;

/**
 * @author <a href="mailto:idler41@163.con">idler41</a> created on 2022-02-19 11:14:03
 */
@Api(tags = "系统菜单接口")
@RestController
@RequestMapping("/sys/menu")
@Slf4j
@Validated
@Log(module = "菜单权限")
public class SysMenuController extends BaseController<SysMenuManager, SysMenuConvert, SysMenu, SysMenuVo, SysMenuQuery> {

    @Autowired
    public SysMenuController(SysMenuManager manager) {
        super(manager, SysMenuConvert.INSTANCE);
    }

    @ApiOperation("查询单个目录")
    @PreAuthorize("sys:menu:view")
    @GetMapping
    public WebResponse<SysMenuVo> get(@RequestParam Integer id) {
        return super.generalGet(id);
    }

    @ApiOperation("根据id批量查询目录")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/batch")
    public WebResponse<List<SysMenuVo>> getByIds(@Validated @Size(min = 1, max = 100) @RequestParam(name = "ids") Set<Integer> ids) {
        return super.generalGetIds(ids);
    }

    @ApiOperation("分页查询目录")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/page")
    public WebPageResponse<SysMenuVo> page(@Validated PageRequest request, @Validated SysMenuQuery query) {
        return super.generalPage(request, query);
    }

    @ApiOperation("添加目录")
    @PreAuthorize("sys:menu:insert")
    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public WebResponse<Serializable> insert(@Validated({Default.class, Custom.class}) @RequestBody SysMenuVo sysMenuVo) {
        // 处理子节点层级数据
        Integer parentId = sysMenuVo.getParentId();
        if (parentId == null || TreeUtil.DEFAULT_ROOT_ID.equals(parentId)) {
            sysMenuVo.setLevelNum(1);
        } else {
            SysMenu parentData = ScopeWebRequestHelper.get(VALID_MENU_ID + sysMenuVo.getParentId(), () -> manager.get(sysMenuVo.getParentId()));
            if (parentData == null) {
                return ResponseHelper.fail(MvcResponseEnum.DATA_NOT_FOUND.getCode(), "父节点数据不存在!");
            }
            sysMenuVo.setLevelNum(parentData.getLevelNum() + 1);
        }
        return super.generalInsert(sysMenuVo);
    }

    @ApiOperation("更新目录(更新所有字段)")
    @PreAuthorize("sys:menu:update")
    @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public WebResponse<Void> update(@Validated({Update.class, Custom.class}) @RequestBody SysMenuVo sysMenuVo) {
        if (isNotNeedUpdateSubTree(sysMenuVo)) {
            return super.generalUpdate(sysMenuVo);
        }
        // 更新所有子节点层级数据
        updateSubTree(sysMenuVo);
        return ResponseHelper.success();
    }

    @ApiOperation("更新目录(只更新非空字段)")
    @PreAuthorize("sys:menu:update")
    @PatchMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public WebResponse<Void> updateSelective(@Validated({UpdateSelective.class, Custom.class}) @RequestBody SysMenuVo sysMenuVo) {
        if (isNotNeedUpdateSubTree(sysMenuVo)) {
            return super.generalUpdateSelective(sysMenuVo);
        }
        // 更新所有子节点层级数据
        updateSubTree(sysMenuVo);
        return ResponseHelper.success();
    }

    @ApiOperation("删除目录")
    @PreAuthorize("sys:menu:delete")
    @DeleteMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public WebResponse<Void> delete(
            @Validated @NotNull @Size(min = 1, max = 100)
            @NoAllocatedData(manager = SysMenuManager.class) @RequestBody Set<Integer> ids) {
        return super.generalDeleteByIds(ids);
    }

    @ApiOperation("查询指定角色权限")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/role/permission")
    public WebResponse<Set<String>> permissionOfRole(@RequestParam Integer roleId) {
        return ResponseHelper.success(manager.findPermissionByRole(roleId));
    }

    @ApiOperation("查询指定用户权限")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/user/permission")
    public WebResponse<Set<String>> permissionOfUser(@RequestParam Integer userId) {
        return ResponseHelper.success(manager.findPermissionByUser(userId));
    }

    @ApiOperation("查询指定角色目录树")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/role/tree")
    public WebResponse<List<SysMenuTreeVo>> treeOfRole(@RequestParam Integer roleId) {
        return ResponseHelper.success(TreeUtil.buildStrictTree(convert.entityToTreeVoList(manager.findByRoleId(roleId))));
    }

    @ApiOperation("查询当前用户目录树")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/tree")
    public WebResponse<List<SysMenuTreeVo>> tree() {
        return ResponseHelper.success(TreeUtil.buildStrictTree(convert.entityToTreeVoList(manager.findTreeByUser(SubjectHelper.getSubjectId()))));
    }

    @ApiOperation("查询指定用户目录树")
    @PreAuthorize("sys:menu:view")
    @GetMapping("/user/tree")
    public WebResponse<List<SysMenuTreeVo>> treeOfUser(@RequestParam Integer userId) {
        return ResponseHelper.success(TreeUtil.buildStrictTree(convert.entityToTreeVoList(manager.findTreeByUser(userId))));
    }

    @ApiOperation("指定角色授权目录")
    @PreAuthorize("sys:menu:grant")
    @PostMapping("/grant")
    public WebResponse<Void> grant(@Validated({Default.class, Custom.class}) @RequestBody SysMenuGrantVo sysMenuGrantVo) {
        manager.grant(sysMenuGrantVo.getRoleId(), convert.grantVoToGrantEntity(sysMenuGrantVo));
        return ResponseHelper.success();
    }

    private void updateSubTree(SysMenuVo sysMenuVo) {
        // 查询整个树
        List<SysMenu> list = manager.findAllOfOrder();
        List<SysMenuTreeVo> tree = TreeUtil.buildStrictTree(convert.entityToTreeVoList(list));
        Map<Integer, SysMenu> map = list.stream().collect(Collectors.toMap(SysMenu::getId, i -> i));

        // 查询当前节点子树
        SysMenuTreeVo oldTreeNode = TreeUtil.get(tree, sysMenuVo.getId());
        if (oldTreeNode == null) {
            log.warn("menu tree not found!menuId={}", sysMenuVo.getId());
            return;
        }
        List<SysMenu> listToUpdate = new ArrayList<>();
        // 深度遍历更新
        TreeUtil.deptTraversal(oldTreeNode, (node, stack) -> {
            SysMenu itemToUpdate, parentRow;
            if (node.getTreeId().equals(sysMenuVo.getId())) {
                // 修改的当前节点
                itemToUpdate = convert.voToEntity(sysMenuVo);
                parentRow = map.get(sysMenuVo.getParentId());
            } else {
                // 修改节点子树节点
                itemToUpdate = map.get(node.getTreeId());
                parentRow = map.get(stack.peek().getTreeId());
            }

            itemToUpdate.setLevelNum(parentRow == null ? 1 : parentRow.getLevelNum() + 1);
            listToUpdate.add(itemToUpdate);
        });
        manager.updateBatchById(listToUpdate);
    }

    private boolean isNotNeedUpdateSubTree(SysMenuVo sysMenuVo) {
        SysMenu row = ScopeWebRequestHelper.get(VALID_MENU_ID + sysMenuVo.getId(), () -> manager.get(sysMenuVo.getId()));
        if (row == null) {
            return true;
        }

        // 父目录未更改
        if (row.getParentId().equals(sysMenuVo.getParentId())) {
            return true;
        }
        SysMenu newParentRow = ScopeWebRequestHelper.get(VALID_MENU_ID + sysMenuVo.getParentId(), () -> manager.get(sysMenuVo.getParentId()));
        if (newParentRow == null) {
            return sysMenuVo.getLevelNum().equals(1);
        }

        SysMenu oldParentRow = ScopeWebRequestHelper.get(VALID_MENU_ID + row.getParentId(), () -> manager.get(row.getParentId()));
        return oldParentRow != null && newParentRow.getLevelNum().equals(oldParentRow.getLevelNum());
    }
}